﻿using System;
using System.Diagnostics;
using GeneralDFS.DataEntity.Enum;
using GeneralDFS.DataEntity.Nodes;
using ProtoBuf;

namespace GeneralDFS.DataEntity.IO
{
    [ProtoContract]
    [Serializable]
    public class FileBlockInfo : FileBlockMetaData,IComparable
    {
        public DataNodeID[] DataNodeList;
        /// <summary>
        /// 保存的都是引用ref
        /// _triplets[3*i]是DataNodeInfoWithBlocks实例，
        /// _triplets[3*i+1]是previous的FileBlockInfo实例，
        /// _triplets[3*i+2]是next的FileBlockInfo实例
        /// 分别作为两个功能
        /// </summary>
        private Object[] _triplets;

        public Boolean HasCopyed = false;

        public FileBlockInfo():this(3){}

        public FileBlockInfo(int replication)
        {
            _triplets = new object[3 * replication];
        }

        public FileBlockInfo(FileBlockMetaData fileBlockMetaData, int replication)
            : this(replication)
        {
            FileName = fileBlockMetaData.FileName;
            Offset = fileBlockMetaData.Offset;
            Guid = fileBlockMetaData.Guid;
            Length = fileBlockMetaData.Length;
            Hash = fileBlockMetaData.Hash;
            FullPath = fileBlockMetaData.FullPath;
        }

        public DataNodeInfoWithBlocks GetDataNodeInfo(int index)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && index * 3 < _triplets.Length);
            return (DataNodeInfoWithBlocks)_triplets[index * 3];
        }

        private FileBlockInfo GetPrevious(int index)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && 3 * index + 1 < _triplets.Length);
            return (FileBlockInfo)_triplets[3 * index + 1];
        }

        public FileBlockInfo GetNext(int index)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && 3 * index + 2 < _triplets.Length);
            return (FileBlockInfo)_triplets[3 * index + 2];
        }

        private void SetDataNodeInfo(int index, DataNodeInfoWithBlocks node, FileBlockInfo previous, FileBlockInfo next)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && 3 * index + 2 < _triplets.Length);
            _triplets[index*3] = node;
            _triplets[index*3 + 1] = previous;
            _triplets[index*3 + 2] = next;
        }

        private FileBlockInfo SetPrevious(int index, FileBlockInfo to)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && 3 * index + 1 < _triplets.Length);
            var info = (FileBlockInfo) _triplets[index*3 + 1];
            _triplets[index*3 + 1] = to;
            return info;
        }

        private FileBlockInfo SetNext(int index, FileBlockInfo to)
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(index >= 0 && 3 * index + 2 < _triplets.Length);
            var info = (FileBlockInfo)_triplets[index * 3 + 2];
            _triplets[index * 3 + 2] = to;
            return info;
        }

        public int GetCapacity()
        {
            Debug.Assert(_triplets != null);
            return _triplets.Length/3;
        }

        public int NumNodes()
        {
            Debug.Assert(_triplets != null);
            Debug.Assert(_triplets.Length%3==0);
            for (var idx = GetCapacity() - 1; idx >= 0; idx--)
            {
                if (GetDataNodeInfo(idx) != null)
                    return idx + 1;
            }
            return 0;
        }

        private int EnsureCapacity(int num)
        {
            Debug.Assert(_triplets!=null);
            var last = NumNodes();
            if (_triplets.Length >= (last + num) * 3)
                return last;
            /* Not enough space left. Create a new array. Should normally 
             * happen only when replication is manually increased by the user. */
            var old = _triplets;
            _triplets = new Object[(last + num) * 3];
            Array.Copy(old, 0, _triplets, 0, last * 3);
            return last;
        }

        public Boolean AddNode(DataNodeInfoWithBlocks node)
        {
            if (FindDataNodeInfo(node) >= 0)
                return false;
            var last = EnsureCapacity(1);
            SetDataNodeInfo(last,node,null,null);
            return true;
        }

        public Boolean RemoveNode(DataNodeInfoWithBlocks node)
        {
            var index = FindDataNodeInfo(node);
            if (index < 0)
                return false;
            Debug.Assert(GetPrevious(index)==null&&GetNext(index)==null);
            var lastNode = NumNodes() - 1;
            SetDataNodeInfo(index,GetDataNodeInfo(lastNode),GetPrevious(lastNode),GetNext(lastNode));
            SetDataNodeInfo(lastNode,null,null,null);
            return true;
        }

        public int FindDataNodeInfo(DataNodeInfoWithBlocks node)
        {
            var len = GetCapacity();
            for (var i = 0; i < len; i++)
            {
                var cur = GetDataNodeInfo(i);
                if (cur == node)
                    return i;
                if(cur==null)
                    break;
            }
            return -1;
        }

        public FileBlockInfo ListInsert(FileBlockInfo head, DataNodeInfoWithBlocks dn)
        {
            var last = this.FindDataNodeInfo(dn);
            Debug.Assert(last>=0);
            Debug.Assert(GetPrevious(last) == null && GetNext(last) == null);
            SetPrevious(last, null);
            SetNext(last, head);
            if (head != null)
                head.SetPrevious(head.FindDataNodeInfo(dn), this);
            return this;
        }

        public FileBlockInfo ListRemove(FileBlockInfo head, DataNodeInfoWithBlocks dn)
        {
            if (head == null) return null;
            var dnIndex = FindDataNodeInfo(dn);
            if (dnIndex < 0)
                return head;
            var next = GetNext(dnIndex);
            var prev = GetPrevious(dnIndex);
            SetNext(dnIndex, null);
            SetPrevious(dnIndex, null);
            if (prev != null)
                prev.SetNext(prev.FindDataNodeInfo(dn), next);
            if (next != null)
                next.SetPrevious(next.FindDataNodeInfo(dn), prev);
            if (this == head)
                head = next;
            return head;
        }

        public FileBlockInfo MoveBlockToHead(FileBlockInfo head, DataNodeInfoWithBlocks dn, int curIndex, int headIndex)
        {
            if (head == this)
                return this;
            var next = SetNext(curIndex, head);
            var prev = SetPrevious(curIndex, null);
            head.SetPrevious(headIndex, this);
            prev.SetNext(prev.FindDataNodeInfo(dn),next);
            if (next != null)
                next.SetPrevious(next.FindDataNodeInfo(dn), prev);
            return this;
        }

        public int CompareTo(object obj)
        {
            if (obj is FileBlockMetaData)
            {
                FileBlockMetaData fileBlockMeta = obj as FileBlockMetaData;
                if (fileBlockMeta.Guid == Guid)
                    return 0;
            }
            if (obj is FileBlockInfo)
            {
                FileBlockInfo fileBlockInfo = obj as FileBlockInfo;
                if (fileBlockInfo.Guid == Guid)
                    return 0;
            }
            return -1;
        }
    }
}